﻿using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Redbrick.Silverlight.Common;
using System.ComponentModel;
using System;

namespace Moria.Common.Model
{
	public class DungeonLevel : Drawable, IEnumerable<CaveFloor>
	{

		#region Consts & Settings

		// Dungeon size parameters					
		const int MAX_HEIGHT = 66;	    // Multiple of 11; >= 22 
		const int MAX_WIDTH = 198;	  // Multiple of 33; >= 66 
		public const int SCREEN_HEIGHT = 22;
		public const int SCREEN_WIDTH = 66;
		public const int QUART_HEIGHT = (SCREEN_HEIGHT / 4);
		public const int QUART_WIDTH = (SCREEN_WIDTH / 4);
		const int MON_SUMMON_ADJ = 2; // Adjust level of summoned creatures    

		#endregion Consts & Settings

		#region Declarations

		private CaveFloor[,] _cave = null;
		private Point _charLocation = new Point();
		private NotifyCollection<InventoryItem> _objects = new NotifyCollection<InventoryItem>();
		private NotifyCollection<Monster> _critters = new NotifyCollection<Monster>();

		private int _height = 0;
		private int _width = 0;

		#endregion Declarations

		#region Constructor

		public DungeonLevel(int level)
		{
			this.Level = level;
			this.init();
		}

		void DungeonLevel_PropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			switch (e.PropertyName)
			{
				default:
					break;
			}
		}

		private void init()
		{
			if (this.Level == 0)
			{
				this.Height = SCREEN_HEIGHT;
				this.Width = SCREEN_WIDTH;
				max_panel_rows = (this.Height / SCREEN_HEIGHT) * 2 - 2;
				max_panel_cols = (this.Width / SCREEN_WIDTH) * 2 - 2;
				panel_row = max_panel_rows;
				panel_col = max_panel_cols;
			}
			else
			{
				this.Height = MAX_HEIGHT;
				this.Width = MAX_WIDTH;
				max_panel_rows = (this.Height / SCREEN_HEIGHT) * 2 - 2;
				max_panel_cols = (this.Width / SCREEN_WIDTH) * 2 - 2;
				panel_row = max_panel_rows;
				panel_col = max_panel_cols;
			}

			this._cave = Sequence.InitArray(this.Width, this.Height, (x, y) => new CaveFloor(this) { X = x, Y = y, });

			this.PropertyChanged += new PropertyChangedEventHandler(DungeonLevel_PropertyChanged);

		}

		void CharacterLocation_PointUpdated(object sender, EventArgs e)
		{
			if (this.CharacterLocationChanged != null)
				this.CharacterLocationChanged.Invoke(this, e);

		}

		#endregion Constructor

		#region Methods
		/* Calculates current boundaries				-RAK-	*/
		void panel_bounds()
		{
			panel_row_min = panel_row * (SCREEN_HEIGHT / 2);
			panel_row_max = panel_row_min + SCREEN_HEIGHT - 1;
			panel_row_prt = panel_row_min - 1;
			panel_col_min = panel_col * (SCREEN_WIDTH / 2);
			panel_col_max = panel_col_min + SCREEN_WIDTH - 1;
			panel_col_prt = panel_col_min - 13;
		}


		public CaveFloor this[int y, int x]
		{
			get
			{
				Debug.Assert(x.InclusiveBetween(0, this.Width - 1) && y.InclusiveBetween(0, this.Height - 1), "this[int y, int x].set");
				return this._cave[x, y];
			}
			set
			{
				Debug.Assert(x.InclusiveBetween(0, this.Width - 1) && y.InclusiveBetween(0, this.Height - 1), "this[int y, int x].get");
				if (this._cave[x, y] != value)
				{
					this._cave[x, y] = value;
				};
			}
		}
		public CaveFloor this[Point pt]
		{
			get
			{
				Debug.Assert(pt.x.InclusiveBetween(0, this.Width - 1) && pt.y.InclusiveBetween(0, this.Height - 1), "this[Point pt].get");
				return this._cave[pt.x, pt.y];
			}
			set
			{
				Debug.Assert(pt.x.InclusiveBetween(0, this.Width - 1) && pt.y.InclusiveBetween(0, this.Height - 1), "this[Point pt].set");
				if (this._cave[pt.x, pt.y] != value)
				{
					this._cave[pt.x, pt.y] = value;
				};
			}
		}

		public Monster MonsterAt(int y, int x)
		{
			Monster mon = this.m_list.FirstOrDefault(m => m.X == x && m.Y == y);
			return mon;
		}
		public InventoryItem ItemAt(int y, int x)
		{
			InventoryItem itm = this.t_list.FirstOrDefault(m => m.X == x && m.Y == y);
			return itm;
		}

		public void DeleteObject(int y, int x)
		{
			Debug.Assert(x.InclusiveBetween(0, this.Width - 1) && y.InclusiveBetween(0, this.Height - 1), "DeleteObject(int y, int x)");
			if (this._cave[x, y].HasItem)
			{
				this.t_list.Remove(this._cave[x, y].ItemPtr);
				//this._cave[x, y].ItemPtr = null;
			}
		}
		public void DeleteObject(InventoryItem itm)
		{
			this.t_list.Remove(itm);
		}
		public IEnumerator<CaveFloor> GetEnumerator()
		{
			for (int y = 0; y < this.Height; y++)
			{
				for (int x = 0; x < this.Width; x++)
				{
					yield return this._cave[x, y];
				}
			}
		}
		IEnumerator IEnumerable.GetEnumerator()
		{
			return this.GetEnumerator();
		}


		#region Stairs

		public void place_stairs(StairType typ, int num, int walls)
		{
			for (int i = 0; i < num; i++)
			{
				bool flag = false;
				do
				{
					int j = 0;
					do
					{
						// Note: don't let y1/x1 be zero, and don't let y2/x2 be equal
						//to cur_height-1/TileWidth-1, these values are always BOUNDARY_ROCK. 
						int y1 = Dice.RandInt(this.Height - 14);
						int x1 = Dice.RandInt(this.Width - 14);
						int y2 = y1 + 12;
						int x2 = x1 + 12;
						do
						{
							do
							{
								CaveFloor cave_ptr = this[y1, x1];
								if (cave_ptr.FloorType <= FloorType.MaxOpenSpace &&
									 (!cave_ptr.HasItem) &&
									 (this.next_to_walls(y1, x1) >= walls))
								{
									flag = true;
									if (typ == StairType.Up) place_up_stairs(y1, x1);
									else place_down_stairs(y1, x1);
								}
								x1++;
							}
							while ((x1 != x2) && (!flag));
							x1 = x2 - 12;
							y1++;
						}
						while ((y1 != y2) && (!flag));
						j++;
					}
					while ((!flag) && (j <= 30));
					walls--;
				}
				while (!flag);
			}
		}

		public void place_down_stairs(Point pt)
		{
			this.place_down_stairs(pt.y, pt.x);
		}
		public void place_down_stairs(int y, int x)
		{
			CaveFloor cave_ptr = this[y, x];
			if (cave_ptr.HasItem) this.DeleteObject(y, x);
			this.t_list.Add(new InventoryItem(Treasure.OBJ_DOWN_STAIR)
			{
				X = x,
				Y = y,
			});

		}

		public void place_up_stairs(Point pt)
		{
			this.place_up_stairs(pt.y, pt.x);
		}
		public void place_up_stairs(int y, int x)
		{
			if (this[y, x].HasItem) this.DeleteObject(x, y);
			this.t_list.Add(new InventoryItem(Treasure.OBJ_UP_STAIR)
			{
				X = x,
				Y = y,
			});
		}

		#endregion Stairs

		#region Doors


		public void place_door(Point pt)
		{
			int tmp = Dice.RandInt(3);
			if (tmp == 1)
			{
				if (Dice.RandInt(4) == 1) place_broken_door(pt);
				else place_open_door(pt);
			}
			else if (tmp == 2)
			{
				tmp = Dice.RandInt(12);
				if (tmp > 3) place_closed_door(pt);
				else if (tmp == 3) place_stuck_door(pt);
				else place_locked_door(pt);
			}
			else place_secret_door(pt);
		}

		public void place_open_door(Point pt)
		{
			this.t_list.Add(new InventoryItem(Treasure.OBJ_OPEN_DOOR)
			{
				X = pt.x,
				Y = pt.y,
			});
			CaveFloor cave_ptr = this[pt];
			cave_ptr.FloorType = FloorType.Corridor;
		}

		public void place_broken_door(Point pt)
		{
			this.place_open_door(pt);
			this.t_list.Last().Misc = 1;
		}

		public void place_closed_door(Point pt)
		{
			this.t_list.Add(new InventoryItem(Treasure.OBJ_CLOSED_DOOR)
			{
				X = pt.x,
				Y = pt.y,
			});
			this[pt].FloorType = FloorType.BlockedFloor;
		}

		public void place_locked_door(int y, int x)
		{
			this.place_locked_door(new Point(x, y));
		}
		public void place_locked_door(Point pt)
		{
			this.place_closed_door(pt);
			this.t_list.Last().Misc = Dice.RandInt(10) + 10;
		}

		public void place_stuck_door(Point pt)
		{
			this.place_closed_door(pt);
			this.t_list.Last().Misc = -1 * (Dice.RandInt(10) + 10);
		}

		public void place_secret_door(int y, int x)
		{
			this.place_secret_door(new Point(x, y));
		}
		public void place_secret_door(Point pt)
		{
			this.t_list.Add(new InventoryItem(Treasure.OBJ_SECRET_DOOR)
			{
				X = pt.x,
				Y = pt.y,
			});
			this[pt].FloorType = FloorType.BlockedFloor;
		}

		#endregion Doors

		#region Item Placement

		public void random_object(Point pt, int num)
		{
			do
			{
				int i = 0;
				do
				{
					int y = pt.y - 3 + Dice.RandInt(5);
					int x = pt.x - 4 + Dice.RandInt(7);
					CaveFloor cave_ptr = this[y, x];
					if (in_bounds(y, x) && (cave_ptr.FloorType <= FloorType.MaxCaveFloor) && (!cave_ptr.HasItem))
					{
						if (Dice.RandInt(100) < 75) place_object(y, x);
						else place_gold(y, x);
						i = 9;
					}
					i++;
				}
				while (i <= 10);
				num--;
			}
			while (num != 0);
		}

		public void place_gold(int y, int x)
		{
			int cur_pos = this.t_list.Count;
			int i = ((Dice.RandInt(this.Level + 2) + 2) / 2) - 1;
			if (Dice.RandInt(Treasure.OBJ_GREAT) == 1) i += Dice.RandInt(this.Level + 1);

			if (i >= Treasure.MAX_GOLD) i = Treasure.MAX_GOLD - 1;

			this.t_list.Add(new InventoryItem(Treasure.OBJ_GOLD_LIST + i)
			{
				X = x,
				Y = y,
			});

			InventoryItem t_ptr = this.t_list.Last();
			t_ptr.Cost += (8 * Dice.RandInt(t_ptr.Cost)) + Dice.RandInt(8);
			//if (this[y,x].cptr == 1)
			//  msg_print ("You feel something roll beneath your feet.");
		}

		public void place_object(int y, int x)
		{
			Debug.Assert(x.InclusiveBetween(0, this.Width - 1) && y.InclusiveBetween(0, this.Height - 1), "place_object(int y, int x)");

			this.t_list.Add(new InventoryItem(Treasure.get_obj_num(this.Level))
			{
				X = x,
				Y = y,
			});
			this.t_list.Last().magic_treasure(this.Level);
			if (this._charLocation.x == x && this._charLocation.y == y)
				this.Message("You feel something roll beneath your feet.");
		}

		public void place_object(Point pt)
		{
			this.place_object(pt.y, pt.x);
		}

		public void place_rubble(int y, int x)
		{
			//TODO: implement place_rubble
		}

		#endregion

		#region Monster Placement

		// TODO: move "public" methods to dungeonlevel class
		public bool summon_monster(ref int y, ref int x, bool slp)
		{
			Point pt = new Point(x, y);
			return this.summon_monster(pt, slp);
			y = pt.y;
			x = pt.x;
		}
		public bool summon_monster(Point pt, bool slp)
		{
			int i = 0;
			bool summon = false;
			int l = CreatureSpecies.get_mons_num(this.Level + MON_SUMMON_ADJ);
			do
			{
				int y = pt.y - 2 + Dice.RandInt(3);
				int x = pt.x - 2 + Dice.RandInt(3);
				if (in_bounds(y, x))
				{
					CaveFloor cave_ptr = this[y, x];
					if (cave_ptr.FloorType <= FloorType.MaxOpenSpace && (!cave_ptr.HasCreature))
					{
						/* Place_monster() should always return TRUE here.  */
						if (!place_monster(y, x, l, slp))
							return false;
						summon = true;
						i = 9;
						pt.y = y;
						pt.x = x;
					}
				}
				i++;
			}
			while (i <= 9);
			return (summon);
		}
		/// <summary>
		/// Places a monster at given location
		/// </summary>
		public bool place_monster(int y, int x, int z, bool slp)
		{
			this.m_list.Add(new Monster(z)
			{
				Y = y,
				X = x
			});
			if (!slp) this.MonsterAt(y, x).SleepCounter = 0;
			return true;
		}

		public void alloc_monster(int num, int dis, bool slp)
		{
			int x = 0;
			int y = 0;
			for (int i = 0; i < num; i++)
			{
				do
				{
					y = Dice.RandInt(this.Height - 2);
					x = Dice.RandInt(Width - 2);
				}
				while (this[y, x].FloorType >= FloorType.MinClosedSpace || (this[y, x].HasCreature) ||
						(distance(y, x, this.Y, this.X) <= dis));

				int l = CreatureSpecies.get_mons_num(this.Level);
				CreatureSpecies critter = new CreatureSpecies(l);
				/* Dragons are always created sleeping here, so as to give the player a sporting chance.  */
				if (critter.Symbol == 'd' || critter.Symbol == 'D') slp = true;
				/* Place_monster() should always return TRUE here.  It does not	 matter if it fails though.  */
				place_monster(y, x, l, slp);
			}
		}

		#endregion Monster Placement

		private int distance(int y1, int x1, int y2, int x2)
		{

			int dy = y1 - y2;
			if (dy < 0) dy = -dy;

			int dx = x1 - x2;
			if (dx < 0) dx = -dx;

			return ((((dy + dx) << 1) - (dy > dx ? dx : dy)) >> 1);
		}
		public bool next_to(int y, int x)
		{
			if (next_to_corr(y, x) > 2)
			{

				if ((this[y - 1, x].FloorType >= FloorType.MinCaveWall) && (this[y + 1, x].FloorType >= FloorType.MinCaveWall))
				{
					return true;
				}
				else if ((this[y, x - 1].FloorType >= FloorType.MinCaveWall) && (this[y, x + 1].FloorType >= FloorType.MinCaveWall))
				{
					return true;
				}
			}
			return false;
		}

		public int next_to_walls(int y, int x)
		{
			int i = 0;

			if (this[y, x - 1].FloorType >= FloorType.MinCaveWall) i++;
			if (this[y, x + 1].FloorType >= FloorType.MinCaveWall) i++;
			if (this[y - 1, x].FloorType >= FloorType.MinCaveWall) i++;
			if (this[y + 1, x].FloorType >= FloorType.MinCaveWall) i++;
			return i;
		}

		public int next_to_corr(int y, int x)
		{
			int result = 0;
			for (int yNdx = y - 1; yNdx <= (y + 1); yNdx++)
				for (int xNdx = x - 1; xNdx <= (x + 1); xNdx++)
				{
					CaveFloor c_ptr = this[yNdx, xNdx];
					// should fail if there is already a door present 
					if (c_ptr.FloorType == FloorType.Corridor &&
						 (!c_ptr.HasItem || c_ptr.ItemPtr.Type < ItemType.MIN_DOORS))
					{
						result++;
					}
				}
			return result;
		}

		public bool in_bounds(int y, int x)
		{
			return x.ExclusiveBetween(0, this.Width) &&
					 y.ExclusiveBetween(0, this.Height);
		}

		#endregion Methods

		#region Events

		public event EventHandler<EventArgs> CharacterLocationChanged;

		#endregion Events

		#region Properties

		public NotifyCollection<InventoryItem> t_list { get { return this._objects; } }
		public NotifyCollection<Monster> m_list { get { return this._critters; } }

		public int panel_row_min { get; private set; }
		public int panel_row_max { get; private set; }
		public int panel_row_prt { get; private set; }
		public int panel_col_min { get; private set; }
		public int panel_col_max { get; private set; }
		public int panel_col_prt { get; private set; }

		public int max_panel_rows { get; private set; }
		public int max_panel_cols { get; private set; }
		public int panel_row { get; private set; }
		public int panel_col { get; private set; }
		public int Level { get; private set; }

		public int Width
		{
			get
			{
				return this._width;
			}
			set
			{
				if (value == this._width) return;
				this._width = value;
				this.RaisePropertyChanged(() => this.Width);
			}
		}
		public int Height
		{
			get
			{
				return this._height;
			}
			set
			{
				if (value == this._height) return;
				this._height = value;
				this.RaisePropertyChanged(() => this.Height);
			}
		}


		//public Point CharacterLocation
		//{
		//    get { return this._charLocation; }
		//    set
		//    {
		//        if (value == this._charLocation) return;
		//        this._charLocation = value;
		//        this.RaisePropertyChanged(() => this.CharacterLocation);
		//    }
		//}

		#endregion Properties


	}

}
